package com.zeny.express.framework.webapp.interceptor;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.struts2.ServletActionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.zeny.express.framework.core.support.SpringContext;
import com.zeny.express.framework.log.annotation.LogAction;
import com.zeny.express.framework.log.entity.ActionLogEntity;
import com.zeny.express.framework.log.log4j.AsyncLoggerFactory;
import com.zeny.express.framework.shared.domain.Constants;
import com.zeny.express.framework.shared.serializer.JsonSerializer;
import com.zeny.express.framework.shared.serializer.Serializer;
import com.zeny.express.framework.shared.util.InetAddressUtils;
import com.zeny.express.framework.shared.util.UUIDUtils;
import com.zeny.express.framework.webapp.action.BaseAction;

/**
 * 框架拦截器
 * 
 * @author zhuwei
 * @date 2013年8月28日 上午11:43:32
 */
public class FrameworkInterceptor extends AbstractInterceptor {

	/**
	 * 序列号
	 */
	private static final long serialVersionUID = 6447921396192827091L;

	/**
	 * 日志
	 */
	protected static final Logger LOGGER = LoggerFactory
			.getLogger(FrameworkInterceptor.class);

	/**
	 * 主机名
	 */
	private String serverHostName;
	
	/**
	 * 序列化
	 */
	@SuppressWarnings("rawtypes")
	private Serializer serializer;
	
	/**
	 * 记录某个action是否需要记录日志
	 */
	private Map<String, Boolean> loggingActions;
	
	/**
	 * 是否debug
	 */
	private boolean debug = false;

	/**
	 * FrameworkInterceptor
	 * 
	 * @author zhuwei
	 * @date 2013年10月12日 上午9:30:24
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public FrameworkInterceptor() {
		try {
			LOGGER.info("[framework] construct FrameworkInterceptor");
			
			serverHostName = InetAddressUtils.getHostName();
			serializer = new JsonSerializer(false, Map.class);
			loggingActions = new ConcurrentHashMap<String, Boolean>();
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}

	/**
	 * 框架拦截器，功能记录日志、时间和做预处理、完结处理
	 * 
	 * @author zhuwei
	 * @date 2013年8月28日 下午12:52:41
	 * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
	 */
	@Override
	public String intercept(ActionInvocation invocation) throws Exception {

		// 记录开始时间
		long beginTime = System.currentTimeMillis();

		Object action = invocation.getAction();
		String actionName = this.getActionName(invocation);

		LOGGER.info("[framework] invoke started: {}", actionName);
		
		// 日志
		ActionLogEntity actionLog = null;
		if (isActionLog(invocation)) {
			actionLog = createActionLog(invocation, new Date(beginTime));
		}

		// 提前执行
		if (action instanceof BaseAction) {
			BaseAction baseAction = (BaseAction) action;
			baseAction.beforeInvoke(invocation);
		}

		String result = invocation.invoke();

		// 后面执行
		if (action instanceof BaseAction) {
			BaseAction baseAction = (BaseAction) action;
			baseAction.afterInvoke(invocation);
		}
		
		// 记录截至时间
		long endTime = System.currentTimeMillis();

		// 日志
		if (isActionLog(invocation)) {
			actionLog.setResponseTime(new Date(endTime));
			actionLog.setActionResult(result);
			AsyncLoggerFactory asyncLoggerFactory = SpringContext.getBean(AsyncLoggerFactory.class);
			asyncLoggerFactory.asyncLogDB(actionLog);
		}

		LOGGER.info("[framework] invoke finished: {} , took time: {} ms.", actionName, (endTime - beginTime));

		return result;
	}
	
	/**
	 * 是否记录日志
	 * 
	 * <pre>
	 * 如果使用LogAction annotation则记录日志
	 * </pre>
	 * 
	 * @author zhuwei
	 * @date 2013年10月12日 下午1:05:01
	 * @param invocation
	 * @return 
	 */
	private boolean isActionLog(ActionInvocation invocation) {

		// 获取方法信息
		Class<?> clazz = invocation.getAction().getClass();
		String methodName = invocation.getProxy().getMethod();
		String loggingAction = clazz.getName() + "!" + methodName;
		
		if (debug) {
			return shouldLog(loggingAction, clazz, methodName);
		}

		// hash表中是否存在
		if (loggingActions.containsKey(loggingAction)) {
			return loggingActions.get(loggingAction);
		} else {
			return shouldLog(loggingAction, clazz, methodName);
		}
		
	}
	
	private boolean shouldLog(String loggingAction, Class<?> clazz, String methodName) {
		
		boolean isLog = false;
		
		try {
			Method method = clazz.getDeclaredMethod(methodName);
			
			LogAction logAction = method.getAnnotation(LogAction.class);
			if (logAction != null) {
				isLog = true;
			} else {
				isLog = false;
			}
			
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			loggingActions.put(loggingAction, isLog);
		}
		
		return isLog;
	}

	/**
	 * 记录具体日志信息
	 * @author zhuwei
	 * @date 2013年10月12日 上午11:58:15
	 * @param invocation
	 * @return 
	 */
	@SuppressWarnings("unchecked")
	private ActionLogEntity createActionLog(ActionInvocation invocation, Date requestTime) {

		HttpServletRequest request = ServletActionContext.getRequest();

		ActionLogEntity actionLog = new ActionLogEntity();

		actionLog.setRequestTime(requestTime);
		actionLog.setId(UUIDUtils.getTimeUUID(requestTime));

		actionLog.setServerName(this.serverHostName);
		actionLog.setServerAddr(request.getLocalAddr());
		actionLog.setServerPort(request.getLocalPort());
		actionLog.setSessionId(request.getSession().getId());

		actionLog.setClientAddr(request.getRemoteAddr());
		actionLog.setClientPort(request.getRemotePort());
		actionLog.setClientUserAgent(request.getHeader("user-agent"));
		actionLog.setClientBrowser(getClientBrowser(request
				.getHeader("user-agent")));

		// TODO
		// actionLog.setAppId(AppSystem.PORTAL);
		// actionLog.setMenuId(action.getMenuId());
		actionLog.setLocale(request.getLocale().toString());
		// actionLog.setClientMac(UserContext.getUserContext().getCookieValue("mac"));
//		IUser user = UserContext.getUserContext().getCurrentUser();
//		if (user != null) {
//			actionLog.setUserCode(user.getUserCode());
//			actionLog.setUserName(user.getDisplayName());
//			actionLog.setOrgCode(user.getOrgCode());
//			actionLog.setOrgName(user.getOrgName());
//		}

		actionLog.setActionClass(invocation.getAction().getClass().getName());
		actionLog.setActionMethod(invocation.getProxy().getMethod());

		String url = request.getRequestURL().toString();
		if (StringUtils.isNotBlank(request.getQueryString())) {
			url += "?" + request.getQueryString();
		}

		actionLog.setUrl(url);
		actionLog.setMethod(request.getMethod());

		byte[] data = serializer.serialize(request.getParameterMap());
		String params = new String(data, Constants.DEFAULT_CHARSET);
		if (params.length() > 2000) {
			actionLog.setParams(params.substring(0, 2000));
		} else {
			actionLog.setParams(params);
		}

		return actionLog;
	}

	/**
	 * 获取客户端浏览器
	 * @author zhuwei
	 * @date 2013年10月12日 上午11:57:06
	 * @param userAgent
	 * @return 
	 */
	private String getClientBrowser(String userAgent) {

		if (userAgent == null)
			return null;

		if (userAgent.indexOf("Maxthon") >= 0) {
			String agent = userAgent.substring(userAgent.indexOf("Maxthon"));
			if (agent.indexOf(" ") >= 0)
				return agent.substring(0, agent.indexOf(" "));
			else
				return agent;
		} else if (userAgent.indexOf("Trident") >= 0) {
			String agent = userAgent.substring(userAgent.indexOf("Trident"));
			if (agent.indexOf(";") >= 0)
				return agent.substring(0, agent.indexOf(";"));
			else
				return agent;
		} else if (userAgent.indexOf("Firefox") >= 0) {
			String agent = userAgent.substring(userAgent.indexOf("Firefox"));
			if (agent.indexOf(" ") >= 0)
				return agent.substring(0, agent.indexOf(" "));
			else
				return agent;
		} else if (userAgent.indexOf("MSIE") >= 0)
			return userAgent.substring(userAgent.indexOf("MSIE") + 2,
					userAgent.indexOf("MSIE") + 8);
		else if (userAgent.indexOf("Chrome") >= 0) {
			String agent = userAgent.substring(userAgent.indexOf("Chrome"));
			return agent.substring(0, agent.indexOf(" "));
		} else if (userAgent.indexOf("Opera") >= 0) {
			String agent = userAgent.substring(userAgent.indexOf("Opera"));
			return agent.substring(0, agent.indexOf(" "));
		} else if (userAgent.indexOf("Safari") >= 0) {
			return userAgent.substring(userAgent.indexOf("Safari"));
		}

		return null;
	}

	/**
	 * 获取方法名称
	 * 
	 * @author zhuwei
	 * @date 2013年8月28日 下午12:45:17
	 * @param invocation
	 * @return
	 */
	private String getActionName(ActionInvocation invocation) {
		StringBuilder name = new StringBuilder(100);
		name.append("[");
		String namespace = invocation.getProxy().getNamespace();
		if ((namespace != null) && (namespace.trim().length() > 0)) {
			name.append(namespace).append("/");
		}
		name.append(invocation.getProxy().getActionName());
		name.append("!");
		name.append(invocation.getProxy().getMethod());
		name.append("]");

		return name.toString();
	}

	/**
	 * @param debug
	 */
	public void setDebug(boolean debug) {
		this.debug = debug;
	}

}
